module IPFS.Client.Utils.SendFilesStream (
  mkSendFileStream,
  defSendFileStreamOptions,
  SendFileStreamOptions(..)
) where
import Prelude

import Data.Argonaut (Json)
import Data.Either (Either(..))
import Data.Maybe (Maybe(..), maybe)
import Data.Nullable (Nullable, notNull, null)
import Data.Tuple.Nested ((/\))
import Effect (Effect)
import Effect.Class.Console (log)
import Effect.Exception (Error)
import Effect.Ref as Ref
import Foreign (Foreign, unsafeToForeign)
import Foreign.Object as O
import IPFS.Client as C
import IPFS.Client.Command (Command, defCommand)
import IPFS.Client.Utils.Multipart (Multipart, getBoundary, mkMultipart)
import IPFS.Client.Utils.PrepareFile (PrepareFileOptions, defPrepareFileOptions, prepareFile)
import IPFS.Client.Utils.SendRequest (RespData)
import IPFS.Client.Utils.SendRequest as Req
import IPFS.Client.Utils.Types (LocalFile, SendFile, SendFileContent(..))
import Network.HTTP as HTTP
import Node.Async (eachSeries)
import Node.SteamEx.Readable as SR
import Node.StreamEx.Stream (fromWritableStream)
import Node.StreamEx.Types (Readable, Writable, defNewWritableOptions, toReadable, toWritable)
import Node.StreamEx.Writable (mkWritable)
import Node.StreamEx.Writable as SW

foreign import encodeURIComponent::String -> String

foreign import emitError::Foreign -> String -> Effect Unit

foreign import emitDrain::Foreign -> Effect Unit

foreign import streamPush::Foreign -> Nullable Foreign -> Effect Unit

genHeaders::SendFile -> O.Object String
genHeaders sendFile = O.fromFoldable ["Content-Disposition" /\ name ,"Content-Type" /\ contentType]
　where
   contentType | sendFile.dir     = "application/x-directory"
               | sendFile.symlink = "application/symlink"
               | otherwise        = "application/octet-stream"
   name = encodeURIComponent sendFile.path


type SendFileStreamOptions = {
  qs::O.Object Json,
  args::Maybe (Array Json),
  progress::Maybe (Int -> Effect Unit)  
}
defSendFileStreamOptions::SendFileStreamOptions
defSendFileStreamOptions = { qs:O.empty, args:Nothing,progress:Nothing}

mkSendFileStream::C.Client -> SendFileStreamOptions -> Maybe PrepareFileOptions -> String -> (Either String　RespData -> Effect Unit) -> Effect (Writable LocalFile)
mkSendFileStream client options mayPrepareFileOptions path callback = do
  ended   <- Ref.new　false
  writing <- Ref.new　false
  let prepareFileOptions = maybe defPrepareFileOptions identity mayPrepareFileOptions
  multipart::Multipart Foreign <- mkMultipart
  retStream::Writable LocalFile <- mkWritable defNewWritableOptions {objectMode = notNull true,_write = _write (toWritable multipart) prepareFileOptions ended writing}
  SW.onFinish (toWritable retStream::Writable LocalFile) (do
    isEnd     <- Ref.read ended
    isWriting <- Ref.read writing
    if (not isEnd) 
     then do
           _ <- Ref.modify (const true) ended
           if (not isWriting) then (SW.end (toWritable multipart::Writable Foreign)　null Nothing Nothing) else pure unit
           pure unit
     else pure unit)
  _boundary <- getBoundary multipart
  let (cmd::Command) = defCommand { path = path, 
                                    qs = options.qs, 
                                    args = options.args, 
                                    multipart = Just true,
                                    multipartBoundary = Just _boundary,
                                    stream = Just true,
                                    recursive = Just true,
                                    progress = options.progress
                                  }
  SW.onError (toWritable multipart::Writable Foreign) (\strErr -> do 
                                                                   emitError (unsafeToForeign retStream) strErr
                                                                   callback $ Left strErr
                                                      )
  req <- Req.requestAPI client cmd　(\_ -> pure unit) (onSendFileResp retStream)
  let reqStream = fromWritableStream $ HTTP.requestAsStream req
  let (readbleMultipart::Readable Foreign) = toReadable multipart
  SW.onDrain reqStream (emitDrain $ unsafeToForeign multipart)
  SR.pipe readbleMultipart reqStream Nothing
  pure retStream
 where
   onSendFileResp::Writable LocalFile -> Either Error (Either String Req.RespData) -> Effect Unit
   onSendFileResp retStream resp = do
    let (foreignRetStream::Foreign) =  unsafeToForeign retStream
    case resp of
      Left err -> callback $ Left (show err)
      Right r  -> case r of 
                    Left errStr -> callback $ Left errStr  
                    Right v     -> callback $ Right v
{-
   onRespJsonStream::NS.Duplex -> Duplex LocalFile -> Effect Unit
   onRespJsonStream jsonStream retStream = do
     let duplexJsonStream = fromDuplex jsonStream::Duplex LocalFile
     let wJsonStream = toWritable duplexJsonStream::Writable LocalFile
     let rJsonStream = toReadable duplexJsonStream::Readable LocalFile
     SW.onError wJsonStream (\str -> emitError (unsafeToForeign retStream) str)
     SR.onData rJsonStream  (\d -> do
                               let (jsonD::O.Object Json) = unsafeCoerce d
                               maybe (pure unit) (\fn -> case (O.lookup "Bytes" jsonD) of
                                                            Nothing      -> pure unit
                                                            Just dBytes  -> let mayInt = caseJsonNumber (Just (-1)) (fromNumber <<< identity) dBytes
                                                                            in  maybe (pure unit) (fn) mayInt
                                                        )  options.progress
                               streamPush (unsafeToForeign retStream) (notNull $ unsafeToForeign d)
                            )
     SR.onEnd rJsonStream (streamPush (unsafeToForeign retStream) null)
     pure unit
-}
   _write::Writable Foreign -> PrepareFileOptions -> Ref.Ref Boolean -> Ref.Ref Boolean -> LocalFile -> String -> (Nullable String -> Effect Unit) -> Effect Unit
   _write multipart pfileOptions ended writing file enc _next = do
     pfiles <- prepareFile [file] pfileOptions
     let sendFiles = map (\sf -> sf {header = genHeaders sf}) pfiles
     eachSeries sendFiles (\sendFile cb -> do
                                            _ <- SW.write multipart (sendFileToJsObject sendFile) Nothing (Just $ cb "")
                                            pure unit) (\err -> do
                                                                  _ <- Ref.modify (const false) writing
                                                                  isEnd <- Ref.read ended
                                                                  if (eq err "") then (if isEnd then (SW.end multipart null Nothing Nothing) *> _next null 
                                                                                                else _next null)
                                                                                 else _next (notNull err)
                                                                  pure unit)
     log "mkSendFileStream write"
     pure unit

sendFileContentToJsObject::SendFileContent -> Foreign
sendFileContentToJsObject (SendBuffer buf)        = unsafeToForeign buf
sendFileContentToJsObject (SendReadable readable) = unsafeToForeign readable
sendFileContentToJsObject  _                      = unsafeToForeign null

sendFileToJsObject::SendFile -> Foreign
sendFileToJsObject send = unsafeToForeign { header:send.header, 
                                            path:send.path, 
                                            symlink:send.symlink,
                                            dir:send.dir,
                                            content:sendFileContentToJsObject send.content}